package com.zrouter.plugin

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Project
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry

class ZRouterPluginTransform extends Transform {
    private Project project

    //注册表的包名
    private String path = "com.z_router.map"
    //注册表的全类名集合
    private List<String> routeList = new ArrayList<>()
    //路由注册类所在的文件
    private File registerDestFile

    ZRouterPluginTransform(Project project) {
        this.project = project
    }

    @Override
    String getName() {
        return ZRouterPluginTransform.class.getSimpleName()
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * 指Transform要操作内容的范围，官方文档Scope有7种类型：
     * <p>
     * EXTERNAL_LIBRARIES        只有外部库
     * PROJECT                   只有项目内容
     * PROJECT_LOCAL_DEPS        只有项目的本地依赖(本地jar)
     * PROVIDED_ONLY             只提供本地或远程依赖项
     * SUB_PROJECTS              只有子项目。
     * SUB_PROJECTS_LOCAL_DEPS   只有子项目的本地依赖项(本地jar)。
     * TESTED_CODE               由当前变量(包括依赖项)测试的代码
     * SCOPE_FULL_PROJECT        整个项目
     */
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        transformInvocation.inputs.each { TransformInput input ->
            //获取当前定义插件的module的所有信息
            input.directoryInputs.each { DirectoryInput directoryInput ->

                //寻找注册表的类
                if (directoryInput.file.isDirectory()) {
                    directoryInput.file.eachFileRecurse { File file ->
                        transformFile(file)
                    }
                } else {
                    transformFile(file)
                }
                // Transform 拷贝文件到 transforms 目录
                File dest = transformInvocation.outputProvider.getContentLocation(
                        directoryInput.getName(),
                        directoryInput.getContentTypes(),
                        directoryInput.getScopes(),
                        Format.DIRECTORY);
                // 将修改过的字节码copy到dest，实现编译期间干预字节码
                FileUtils.copyDirectory(directoryInput.getFile(), dest)
            }

            //获取其依赖的包的所有信息
            input.jarInputs.each { JarInput jarInput ->
                def jarName = jarInput.name

                if (jarName.endsWith(".jar")) {
                    jarName = jarName.substring(0, jarName.length() - 4)
                }

                //源文件
                File src = jarInput.file

                //目标文件
                def dest = transformInvocation.outputProvider.getContentLocation(jarName + "_temp",
                        jarInput.contentTypes, jarInput.scopes, Format.JAR)

                //扫描文件，寻找需要的类
                scanJar(src, dest)

                FileUtils.copyFile(src, dest)
            }
        }

        //万事俱备，现在修改字节码
        if (registerDestFile != null) {
            //打印文件名
            project.logger.error(routeList.toString())
            //判断是否是jar文件
            if (registerDestFile.name.endsWith(".jar")) {
                //生成临时文件
                def optJar = new File(registerDestFile.getParent(), registerDestFile.name + ".opt")
                if (optJar.exists()) {
                    optJar.delete()
                }

                //遍历所有的类
                def file = new JarFile(registerDestFile)
                Enumeration enumeration = file.entries()
                JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))

                while (enumeration.hasMoreElements()) {
                    JarEntry jarEntry = enumeration.nextElement()
                    String entryName = jarEntry.name
                    ZipEntry zipEntry = new ZipEntry(entryName)
                    InputStream inputStream = file.getInputStream(jarEntry)
                    jarOutputStream.putNextEntry(zipEntry)
                    if (entryName == "com/study/zrouter/ZRouter.class") {
                        project.logger.error("找到路由类,开始插桩")
                        ClassReader cr = new ClassReader(inputStream)
                        ClassWriter cw = new ClassWriter(cr, 0)
                        //掉用ASM api
                        ClassVisitor cv = new ZRouterPluginClassVisitor(cw, routeList)
                        cr.accept(cv, ClassReader.EXPAND_FRAMES)
                        jarOutputStream.write(cw.toByteArray())
                    } else {
                        jarOutputStream.write(IOUtils.toByteArray(inputStream))
                    }
                    inputStream.close()
                    jarOutputStream.closeEntry()
                }
                jarOutputStream.close()
                file.close()
                if (registerDestFile.exists()) {
                    registerDestFile.delete()
                }
                optJar.renameTo(registerDestFile)
            }
        }
    }

    //扫描所有的jar文件，找到需要的类进行插装
    void scanJar(File jarFile, File destFile) {
        if (jarFile != null) {
            def file = new JarFile(jarFile)
            Enumeration enumeration = file.entries()
            while (enumeration.hasMoreElements()) {
                JarEntry jarEntry = enumeration.nextElement()
                String entryName = jarEntry.getName()
                if (entryName.startsWith("com/z_router/map")) {
                    routeList.add(entryName.replace("/", ".").replace(".class", ""))
                } else if (entryName == "com/study/zrouter/ZRouter.class") {
                    //找到需要插装的路由类，打印文件名
                    project.logger.error(entryName)
                    registerDestFile = destFile
                }
            }
            file.close()
        }
    }

    // 在主工程找到路由注册表的类（如果有的话）
    private void transformFile(File file) throws IOException {
        def name = file.name
        if (name.startsWith("ZRouter\$\$")) {
            routeList.add(path + "." + name.replace(".class", ""))
        }
    }
}
